package com.guli.mall.wms.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.guli.common.constant.WareConstant;
import com.guli.common.pojo.vo.SkuItemVo;
import com.guli.common.pojo.vo.StatusCodeEnum;
import com.guli.common.to.LockStockInfoTo;
import com.guli.common.to.SkuHasStockTo;
import com.guli.common.to.StockWareTo;
import com.guli.common.utils.CopyPageProperties;
import com.guli.mall.serviceBase.globalException.MallException;
import com.guli.mall.wms.entity.*;
import com.guli.mall.wms.entity.vo.WareSkuNameVo;
import com.guli.mall.wms.feign.ProductServiceFeignClient;
import com.guli.mall.wms.service.WareInfoService;
import com.guli.mall.wms.service.WareOrderTaskDetailService;
import com.guli.mall.wms.service.WareOrderTaskService;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.guli.common.utils.PageUtils;

import com.guli.mall.wms.mapper.WareSkuMapper;
import com.guli.mall.wms.service.WareSkuService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;

@Aspect
@Service("wareSkuService")
@Slf4j
public class WareSkuServiceImpl extends ServiceImpl<WareSkuMapper, WareSkuEntity> implements WareSkuService {

    @Resource
    WareSkuService wareSkuService;

    @Resource
    WareInfoService wareInfoService;

    @Resource
    ProductServiceFeignClient productServiceFeignClient;

    @Resource
    WareSkuMapper wareSkuMapper;

    @Resource
    RabbitTemplate rabbitTemplate;

    @Resource
    WareOrderTaskDetailService wareOrderTaskDetailService;

    @Resource
    WareOrderTaskService wareOrderTaskService;

    @Override
    public PageUtils<WareSkuNameVo> queryPage(Integer currentPage, Integer size, Map<String, String> map) {

        Page<WareSkuEntity> page = new Page<>(currentPage, size);

        QueryWrapper<WareSkuEntity> wrapper = new QueryWrapper<>();

        String key = map.get("key");

        if (!StringUtils.isEmpty(key)) {
            if (Pattern.matches("^[0-9]+$", key)) {
                wrapper.eq("id", key);
            } else {
                wrapper.like("sku_name", key);
            }

        }

        String skuId = map.get("skuId");
        if (!StringUtils.isEmpty(skuId)) {
            wrapper.eq("sku_id", skuId);
        }

        String wareId = map.get("wareId");
        if (!ObjectUtils.isEmpty(wareId)) {
            wrapper.eq("ware_id", wareId);
        }


        Page<WareSkuEntity> resultPage = this.page(page, wrapper);

        Page<WareSkuNameVo> copy = CopyPageProperties.copy(resultPage, WareSkuNameVo.class);

        for (WareSkuNameVo skuVo : copy.getRecords()) {

            if (!ObjectUtils.isEmpty(skuVo.getSkuId())) {

                Map<String, String> infoAndReturnTo = productServiceFeignClient.getInfoAndReturnTo(skuVo.getSkuId());

                String skuName = infoAndReturnTo.get("skuName");

                if (!ObjectUtils.isEmpty(skuName)) {

                    skuVo.setSkuNameTitle(skuName);
                }
            }


            if (!ObjectUtils.isEmpty(skuVo.getWareId())) {

                WareInfoEntity wareInfoEntity = wareInfoService.getById(skuVo.getWareId());

                if (!ObjectUtils.isEmpty(wareInfoEntity)) {

                    skuVo.setWareName(wareInfoEntity.getName());
                }
            }

        }
        return new PageUtils<>(copy);
    }

    @Override
    public void updateWareSkuStockByPurchaseDetail(List<PurchaseDetailEntity> purchaseDetailListForUpdating) {

        for (PurchaseDetailEntity purchaseDetailEntity : purchaseDetailListForUpdating) {

            QueryWrapper<WareSkuEntity> wrapper = new QueryWrapper<>();

            wrapper.eq("ware_id", purchaseDetailEntity.getWareId());

            wrapper.eq("sku_id", purchaseDetailEntity.getSkuId());

            int count = this.count(wrapper);

            if (count <= 0) {
                // 库存中还没有sku_id 和 ware_id 对应的记录，做新增操作

                WareSkuEntity wareSkuEntity = new WareSkuEntity();

                wareSkuEntity.setSkuId(purchaseDetailEntity.getSkuId());

                wareSkuEntity.setWareId(purchaseDetailEntity.getWareId());

                wareSkuEntity.setStock(purchaseDetailEntity.getSkuNum());

                // 设置 wms_ware_sku 的冗余字段 sku_name，我们不希望因为这个冗余字段因为调用微服务失败而导致整个事务回滚，所以捕获异常，不被transaction注解捕获
                try {
                    Map<String, String> infoAndReturnTo = productServiceFeignClient.getInfoAndReturnTo(purchaseDetailEntity.getSkuId());
                    String skuName = infoAndReturnTo.get("skuName");

                    wareSkuEntity.setSkuName(skuName);
                } catch (Exception e) {

                }

                this.save(wareSkuEntity);
            } else {
                // 直接在数据库上进行添加操作
                wareSkuMapper.addStock(purchaseDetailEntity.getSkuId(), purchaseDetailEntity.getWareId(), purchaseDetailEntity.getSkuNum());

            }



            /*
            QueryWrapper<WareSkuEntity> wrapper = new QueryWrapper<>();

            wrapper.eq("ware_id", purchaseDetailEntity.getWareId());

            wrapper.eq("sku_id", purchaseDetailEntity.getSkuId());

            WareSkuEntity wareSkuEntity = this.getOne(wrapper, false);

            if(ObjectUtils.isEmpty(wareSkuEntity)){
                throw new MallException("wareId = "+purchaseDetailEntity.getWareId()+"，skuId = "+purchaseDetailEntity.getSkuId()+" 对应的ware-sku 表中没有记录");
            }





            WareSkuEntity wareSkuEntityForUpdating = new WareSkuEntity();

            wareSkuEntityForUpdating.setId(wareSkuEntity.getId());

            // 更新后的库存 = 原有的库存数量 + 采购回来的库存数量
            wareSkuEntityForUpdating.setStock(purchaseDetailEntity.getSkuNum() + wareSkuEntity.getStock());

            this.updateById(wareSkuEntityForUpdating);*/
        }
    }

    @Override
    public WareSkuEntity getWareSkuBySkuId(Long skuId) {

        QueryWrapper<WareSkuEntity> wrapper = new QueryWrapper<>();

        wrapper.eq("sku_id", skuId);

        return getOne(wrapper, false);
    }

    @Override
    public List<SkuHasStockTo> judgeStockExist(List<Long> skuId) {

        QueryWrapper<WareSkuEntity> wrapper = new QueryWrapper<>();

        wrapper.in("sku_id", skuId);

        wrapper.select("stock", "sku_id", "stock_locked");

        List<WareSkuEntity> list = this.list(wrapper);

        return list.stream().filter(ware -> {

            return !ObjectUtils.isEmpty(ware.getStock())
                    && !ObjectUtils.isEmpty(ware.getStockLocked())
                    && ware.getStock() - ware.getStockLocked() > 0;

        }).map(ware -> {
            SkuHasStockTo skuHasStockTo = new SkuHasStockTo();

            skuHasStockTo.setSkuId(ware.getSkuId());

            skuHasStockTo.setHasStock(true);
            return skuHasStockTo;
        }).collect(Collectors.toList());

    }

    @Override
    public Map<Long, Integer> getStockBySkuIdArray(List<Long> skuIds) {

        QueryWrapper<WareSkuEntity> wrapper = new QueryWrapper<>();

        wrapper.in("sku_id", skuIds);

        List<WareSkuEntity> list = this.list(wrapper);

        HashMap<Long, Integer> map = new HashMap<>();

        list.parallelStream().forEach(wareSkuEntity -> {
            //总库存 - 锁定库存 = 可用库存
            map.put(wareSkuEntity.getSkuId(), wareSkuEntity.getStock() - wareSkuEntity.getStockLocked());
        });

        return map;
    }

    @Transactional
    @Override
    public void lockWareStock(StockWareTo stockWareTo) {

        List<LockStockInfoTo> lockStockInfoTos = new ArrayList<>(stockWareTo.getItemVos().size());

        WareOrderTaskEntity wareOrderTaskEntity = new WareOrderTaskEntity();

        wareOrderTaskEntity.setOrderSn(stockWareTo.getOrderSn());

        wareOrderTaskEntity.setOrderId(stockWareTo.getOrderId());

        wareOrderTaskService.save(wareOrderTaskEntity);

        for (SkuItemVo skuItemVo : stockWareTo.getItemVos()) {

            List<Long> wareIds = getBaseMapper().getWareIdThatHasEnoughStockItem(skuItemVo.getSkuId(), skuItemVo.getCount());

            if (CollectionUtils.isEmpty(wareIds)) {
                throw new MallException(StatusCodeEnum.THERE_IS_NOT_STOCK_FOR_SKU_ID.getCode(),
                        "skuItemId = " + skuItemVo.getSkuId() + "的商品在所有仓库中都没有库存");
            }

            //TODO 选择一个离收货地址最近的仓库（根据距离从近到远排序（收货地址与仓库间的距离））

            Integer affectRow = 0;
            for (Long selectedWareId : wareIds) {

                affectRow = getBaseMapper().lockStock(skuItemVo.getSkuId(), skuItemVo.getCount(), selectedWareId);

                if (affectRow > 0) {
                    // 锁定成功，跳出循环

                    //将锁库存的信息写入到数据库（如果使用消息队列的接受者那边处理，更新锁库存信息的时候不好获取id，所以在这里处理）
                    WareOrderTaskDetailEntity wareOrderTaskDetailEntity = new WareOrderTaskDetailEntity(null, skuItemVo.getSkuId(), skuItemVo.getSkuName(), skuItemVo.getCount(), wareOrderTaskEntity.getId(), selectedWareId, WareConstant.LockStatus.LOCKED);

                    wareOrderTaskDetailService.save(wareOrderTaskDetailEntity);

                    lockStockInfoTos.add(new LockStockInfoTo(skuItemVo.getSkuId(), wareOrderTaskDetailEntity.getId(), skuItemVo.getSkuName(), skuItemVo.getCount(), selectedWareId));
                    break;
                }
            }

            if (affectRow == 0) {
                // 遍历所有仓库，仍然没有指定数量的库存，并且锁定失败
                throw new MallException(
                        StatusCodeEnum.THERE_IS_NOT_STOCK_FOR_SKU_ID.getCode(),
                        String.format("所有仓库中，没有任何一个仓库有%s件skuId = %s 的商品", skuItemVo.getCount(), skuItemVo.getSkuId())
                );
            }

        }



        // 设置锁库存的信息
        stockWareTo.setLockStockInfoTos(lockStockInfoTos);

        // 把订单号作为消息队列中的关联数据
        CorrelationData correlationData = new CorrelationData(stockWareTo.getOrderId().toString());
        /**
         * 库存锁定成功，告诉MQ，锁库存时间到，去查询是否有对应的已经支付了的订单
         * 如果没有，就解锁库存
         * */
        rabbitTemplate.convertAndSend(WareConstant.STOCK_EVENT_EXCHANGE, WareConstant.STOCK_LOCKED_DELAY_ROUTING_KEY, stockWareTo, correlationData);

    }


    /**
     * 解锁库存
     */
    @Transactional
    public void unlockStock(List<LockStockInfoTo> lockStockInfoTos) {

        for (LockStockInfoTo lockStockInfoTo : lockStockInfoTos) {

            // 更新时，添加了额外的更新条件，只有是已经锁定的记录才能被解锁（额外添加的条件：只有库存是锁定状态才能解锁）
            // 因为要保证幂等性
            Boolean success = wareOrderTaskDetailService.updateWareOrderTaskDetailStatusByIdAndOldStatus(lockStockInfoTo.getWareOrderTaskDetailEntityId(),WareConstant.LockStatus.LOCKED,WareConstant.LockStatus.UNLOCKED);

            int affectRows =0 ;

            if(success) {
                log.info("更新库存锁定记录wareOrderTaskDetailId = {}，的锁定状态为{}", lockStockInfoTo.getWareOrderTaskDetailEntityId(), WareConstant.LockStatus.UNLOCKED.getMessage());

                affectRows = getBaseMapper().unlockStock(lockStockInfoTo.getCount(), lockStockInfoTo.getSkuId(), lockStockInfoTo.getWareId());

            }else{
                log.warn("更新库存锁定记录wareOrderTaskDetailId = {}，的锁定状态失败，可能是其他消息消费者已经对其进行过修改",lockStockInfoTo.getWareOrderTaskDetailEntityId());
            }


            if (affectRows > 0) {
                // 解锁成功
                log.info("{}在仓库id = {}解锁{}件成功",lockStockInfoTo.getSkuName(),lockStockInfoTo.getWareId(),lockStockInfoTo.getCount());
            }else{
                log.warn("{}在仓库id = {}解锁{}件失败，可能是其他消息消费者已经对其进行过修改",lockStockInfoTo.getSkuName(),lockStockInfoTo.getWareId(),lockStockInfoTo.getCount());
            }

        }
    }

    @Transactional
    @Override
    public void unlockStockByOrderId(Long orderId) {
        // 1、根据 orderId 来查询库存工作单

        WareOrderTaskEntity wareOrderTaskEntity = wareOrderTaskService.getWareOrderTaskByOrderId(orderId);

        Long wareOrderTaskEntityId = wareOrderTaskEntity.getId();

        List<WareOrderTaskDetailEntity> wareOrderTaskDetailEntities  = wareOrderTaskDetailService.getByOrderTaskId(wareOrderTaskEntityId);

        // 2、封装为 LockStockInfoTo 对象，就能调用本对象中的方法进行解锁库存（不用自己再写一遍方法）
        List<LockStockInfoTo> collect = wareOrderTaskDetailEntities.stream().map(wareOrderTaskDetailEntity -> {
            LockStockInfoTo lockStockInfoTo = new LockStockInfoTo();

            lockStockInfoTo.setCount(wareOrderTaskDetailEntity.getSkuNum());

            lockStockInfoTo.setSkuId(wareOrderTaskDetailEntity.getSkuId());

            lockStockInfoTo.setWareId(wareOrderTaskDetailEntity.getWareId());

            lockStockInfoTo.setWareOrderTaskDetailEntityId(wareOrderTaskDetailEntity.getId());

            lockStockInfoTo.setSkuName(wareOrderTaskDetailEntity.getSkuName());

            return lockStockInfoTo;
        }).collect(Collectors.toList());

        // 使用 aop 来调用本类的其他方法，其他类有 @Transactional 注解，为使其生效
        /*WareSkuServiceImpl wareSkuService = (WareSkuServiceImpl)AopContext.currentProxy();

        wareSkuService.unlockStock(collect);*/

        unlockStock(collect);

        // 2、设置库存细节工作单的锁库存状态
        /*for (WareOrderTaskDetailEntity wareOrderTaskDetailEntity : wareOrderTaskDetailEntities) {

            QueryWrapper<WareOrderTaskDetailEntity> wrapper = new QueryWrapper<>();

            wrapper.eq("id",wareOrderTaskDetailEntity.getId());

            wrapper.eq("lock_status",WareConstant.LockStatus.LOCKED);

            WareOrderTaskDetailEntity entity = new WareOrderTaskDetailEntity();

            entity.setLockStatus(WareConstant.LockStatus.UNLOCKED);

            boolean update = wareOrderTaskDetailService.update(entity, wrapper);

            if(update){
                log.info("库存细节工作单id = {}，已解锁",wareOrderTaskDetailEntity.getId());
            }else{
                log.error("库存细节工作单id = {}，解锁失败",wareOrderTaskDetailEntity.getId());
            }



        }*/
    }

    @Transactional
    @Override
    public boolean deductStockByWareOrderTaskDetailIds(List<Long> wareOrderTaskDetailIds) {

        List<WareOrderTaskDetailEntity> wareOrderTaskDetailEntities = wareOrderTaskDetailService.getBaseMapper().selectBatchIds(wareOrderTaskDetailIds);

        for (WareOrderTaskDetailEntity wareOrderTaskDetailEntity : wareOrderTaskDetailEntities) {

            int affectRows = this.getBaseMapper().deductStockAndDeductStockLocked(wareOrderTaskDetailEntity.getSkuNum(),wareOrderTaskDetailEntity.getSkuId(),wareOrderTaskDetailEntity.getWareId());

            if(affectRows==0){
                throw new MallException(StatusCodeEnum.DEDUCT_STOCK_FAIL,
                        String.format("扣减wareId = %s 的商品 skuId = %s ，扣减 %s 件 失败",wareOrderTaskDetailEntity.getWareId(),wareOrderTaskDetailEntity.getSkuId(),wareOrderTaskDetailEntity.getSkuName()));
            }
        }

        return true;
    }

    /**
     * 记录锁库存的操作
     */
    @Transactional
    public List<WareOrderTaskDetailEntity> recordLockStock(StockWareTo stockWareTo) {

        List<LockStockInfoTo> lockStockInfoTos = stockWareTo.getLockStockInfoTos();
        log.info("把锁库存信息写入数据库 wms_ware_order_task_detail 表，要锁定的数据是：{}", lockStockInfoTos);

        WareOrderTaskEntity wareOrderTaskEntity = new WareOrderTaskEntity();

        wareOrderTaskEntity.setOrderId(stockWareTo.getOrderId());

        wareOrderTaskEntity.setOrderSn(stockWareTo.getOrderSn());

        wareOrderTaskService.save(wareOrderTaskEntity);

        return lockStockInfoTos.parallelStream().map(lockStockInfoTo -> {
            WareOrderTaskDetailEntity wareOrderTaskDetailEntity = new WareOrderTaskDetailEntity();

            wareOrderTaskDetailEntity.setWareId(lockStockInfoTo.getWareId());

            wareOrderTaskDetailEntity.setSkuId(lockStockInfoTo.getSkuId());

            wareOrderTaskDetailEntity.setSkuName(lockStockInfoTo.getSkuName());

            wareOrderTaskDetailEntity.setSkuNum(lockStockInfoTo.getCount());

            wareOrderTaskDetailEntity.setTaskId(wareOrderTaskEntity.getId());

            wareOrderTaskDetailEntity.setLockStatus(WareConstant.LockStatus.LOCKED);

            wareOrderTaskDetailService.save(wareOrderTaskDetailEntity);
            /**
             * 这里不能只返回 wareOrderTaskDetailEntity 的id，因为本方法和调用本方法的父方法处于同一个事务中
             * 如果失败了，那么事务将回滚，其他消息接受者就不能根据此id查询到指定的锁库存记录
             * */
            return wareOrderTaskDetailEntity;
        }).collect(Collectors.toList());


    }

}